package com.yyfax.account.service;

import com.jaws.core.common.log.Log;
import com.jaws.core.common.util.DateUtil;
import com.yyfax.account.common.enums.DayChangeStateEnum;
import com.yyfax.account.common.exception.AccountExceptionCode;
import com.yyfax.account.common.util.JobContextUtils;
import com.yyfax.account.dao.AccountFlowDao;
import com.yyfax.account.dao.AccountInfoDao;
import com.yyfax.account.dao.AccountTradeDao;
import com.yyfax.account.dao.DayChangeRecordDao;
import com.yyfax.account.dao.DayChangeResultDao;
import com.yyfax.account.domain.DayChangeRecord;
import com.yyfax.account.vo.JobContext;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 日切
 *
 * @author maojian
 * @date 2017-09-05 09:38:03
 * @since v1.0.0
 */
@Service
public class DayChangeService {
	private static DateTimeFormatter formatter = DateTimeFormat.forPattern(DateUtil.FORMAT_DATE);
	private Logger logger = LoggerFactory.getLogger(getClass());
	@Resource
	private AccountTradeDao accountTradeDao;
	@Resource
	private AccountFlowDao accountFlowDao;
	@Resource
	private DayChangeResultDao dayChangeResultDao;
	@Resource
	private DayChangeRecordDao dayChangeRecordDao;
	@Resource
	private AccountInfoDao accountInfoDao;
	@Resource
	private DayChangeTransService dayChangeTransService;

	/**
	 * 异步处理日切任务
	 *
	 * @author liangqm
	 * @date 2017-10-25 11:41:18
	 * @params context
	 * @since v1.0.0
	 */
	@Async
	public void syncDoDayChange(JobContext context) {
		this.doDayChange(context);
		MDC.clear();
	}

	/**
	 * 开始处理日切任务
	 *
	 * @author liangqm
	 * @date 2017-10-25 11:41:18
	 * @params context
	 * @since v1.0.0
	 */
	public void doDayChange(JobContext context) {
		final String op = "DayChangeService.doDayChange";

		// 获取日切日期，默认为当前日期
		DateTime jobDay = JobContextUtils.getJobDay(context);
		logger.info(Log.op(op).kv("jobDay", formatter.print(jobDay)).msg("开始执行日切job").toString());

		// 1.查找距离jobDay最近的一笔日切记录
		DayChangeRecord lastRecord = dayChangeRecordDao.queryLatestByDate(formatter.print(jobDay));

		// 2.检查最新日切任务记录情况,不符合要求直接返回
		if (!checkLastRecord(lastRecord, jobDay)) {
			return;
		}

		// 3.执行从successChangeDay + 1 到appointDate的日切
		DateTime successChangeDay = formatter.parseDateTime(lastRecord.getChangeDay());
		while (successChangeDay.isBefore(jobDay)) {
			successChangeDay = successChangeDay.plusDays(1);
			logger.info(Log.op(op).kv("changeDay", formatter.print(successChangeDay)).msg("begin").toString());

			// 创建正在执行的日切记录
			DayChangeRecord newRecord = new DayChangeRecord();
			newRecord.setChangeDay(formatter.print(successChangeDay));
			newRecord.setState(DayChangeStateEnum.DEALING.getValue());
			dayChangeRecordDao.insert(newRecord);

			// 执行日切任务
			dayChangeTransService.doDayChangeByDate(successChangeDay, newRecord);

			// 查询日切任务情况,更新日切任务状态
			boolean result = checkRecordExecuteState(formatter.print(successChangeDay));

			// 任务执行失败，跳出循环不继续执行
			if (!result) {
				logger.error(Log.op(op).kv("changeDay", formatter.print(successChangeDay)).msg("执行日切任务失败").toString());
				throw AccountExceptionCode.DAY_CHANGE_TASK_FAILURE.exp();
			}

			logger.info(Log.op(op).kv("changeDay", formatter.print(successChangeDay)).msg("end").toString());
		}
	}

	/**
	 * 检查最新日切任务记录情况
	 * 1.日切任务不能为null;
	 * 2.状态为:DEALING、REDO、SUCCESS直接返回
	 * 3.如果日切任务状态为FAIL,必须重新运行成功后才能执行后续操作
	 *
	 * @return boolean
	 * @author liangqm
	 * @date 2017-10-26 20:20:18
	 * @params lastRecord
	 * @params jobDay
	 * @since v1.0.0
	 */
	private boolean checkLastRecord(DayChangeRecord lastRecord, DateTime jobDay) {
		final String op = "DayChangeService.checkLastRecord";

		// 1如果account_day_change_record 没有数据，直接报错结束任务
		if (null == lastRecord) {
			logger.warn(Log.op(op).kv("jobDay", formatter.print(jobDay)).msg("指定执行日切日切前有没执行成功的日切记录").toString());
			throw AccountExceptionCode.DAY_CHANGE_RECON_NULL.exp(formatter.print(jobDay));
		}

		// 2 判断日切任务执行状态;状态为:DEALING、REDO、SUCCESS直接返回;
		DateTime successChangeDay = formatter.parseDateTime(lastRecord.getChangeDay());

		// 当天日期已完成，不再执行，直接返回
		if (successChangeDay.equals(jobDay)) {
			if (DayChangeStateEnum.SUCCESS.isValue(lastRecord.getState())) {
				logger.warn(Log.op(op).kv("changeDay", lastRecord.getChangeDay()).msg("日切已经执行成功，当前请求直接返回").toString());
				return false;
			}
		}

		if (DayChangeStateEnum.DEALING.isValue(lastRecord.getState()) || DayChangeStateEnum.REDOING.isValue(lastRecord.getState())) {
			logger.warn(Log.op(op).kv("changeDay", lastRecord.getChangeDay()).msg("日切正在执行，当前请求直接返回").toString());
			return false;
		}

		// 3最新日切记录状态为失败，重新执行日切任务
		if (DayChangeStateEnum.FAIL.isValue(lastRecord.getState())) {
			return redoLastRecord(lastRecord);
		}

		return true;
	}

	/**
	 * 查询任务执行情况，成功返回true 失败返回 false
	 *
	 * @return boolean
	 * @author liangqm
	 * @date 2017-10-16 19:57:18
	 * @params changeDay
	 * @since v1.0.0
	 */
	private boolean checkRecordExecuteState(String changeDay) {
		DayChangeRecord newRecord = dayChangeRecordDao.queryLatestByDate(changeDay);

		// 执行成功
		if (!DayChangeStateEnum.FAIL.getValue().equals(newRecord.getState())) {
			newRecord.setState(DayChangeStateEnum.SUCCESS.getValue());
			dayChangeRecordDao.update(newRecord);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 重新运行执行失败的任务
	 *
	 * @return boolean
	 * @author liangqm
	 * @date 2017-10-16 20:04:18
	 * @params lastRecord
	 * @since v1.0.0
	 */
	private boolean redoLastRecord(DayChangeRecord lastRecord) {
		final String op = "DayChangeService.redoLastRecord";

		// 指定日切日期
		String changeDay = lastRecord.getChangeDay();
		logger.info(Log.op(op).kv("changeDay", changeDay).msg("重新执行日切任务 -- begin").toString());

		// 更新 record 状态
		lastRecord.setState(DayChangeStateEnum.REDOING.getValue());
		dayChangeRecordDao.update(lastRecord);

		// 重新执行执行日切任务
		dayChangeTransService.doDayChangeByDate(DateTime.parse(changeDay, formatter), lastRecord);

		// 查询日切任务情况,更新日切任务状态
		boolean result = checkRecordExecuteState(changeDay);

		// 任务执行失败，直接返回不继续运行（后续日切基于这一天数据，当天不成功下一日不能运行）
		if (!result) {
			logger.error(Log.op(op).kv("changeDay", changeDay).msg("执行日切任务失败").toString());
			return false;
		}

		logger.info(Log.op(op).kv("changeDay", changeDay).msg("重新执行 -- end").toString());
		return true;
	}
}